package subterfuge;

import emergence.entities.Entity;
import emergence.game.Tickable;
import emergence.game.level.EntityMap;
import emergence.game.level.Level;
import emergence.game.level.Level.LevelResize;
import emergence.game.level.LevelMap;
import emergence.game.level.tiles.Tile;
import emergence.resource.ResourceInputFile;
import emergence.resource.Resources;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.filechooser.FileFilter;
import subterfuge.dialog.CropLevelDialog;
import subterfuge.dialog.ExpandLevelDialog;
import util.Logger;

public class SubterfugeEditor implements Runnable, Tickable {

    public final static String VERSION = "0.0.1";
    public final static String NAME = "Subterfuge v" + VERSION;

    private SubterfugeWindowListener windowListener;

    private Level level;
    private List<Tile> tiles = new ArrayList<>();

    private final Logger logger = Logger.getDefaultLogger();

    private boolean animateTiles = true;

    private JFrame frame;
    private JMenuBar menuBar;

    private JMenu fileMenu;
    private JMenuItem fileNewItem;
    private JMenuItem fileOpenItem;
    private JMenuItem fileSaveItem;
    private JMenuItem fileSaveAsItem;
    private FileMenuListener fileMenuListener = new FileMenuListener();

    private JMenu levelMenu;
    private JMenuItem levelExpandItem;
    private JMenuItem levelCropItem;
    private final LevelMenuListener levelMenuListener = new LevelMenuListener();

    private JMenu resourceMenu;
    private JMenuItem loadResourceItem;
    private ResourceMenuListener resMenuListener = new ResourceMenuListener();

    private JMenu tileMenu;
    private JMenuItem editTileItem;

    private ViewerWindow viewer;
    private JPanel viewerWrapper;

    private LeftSidebar leftSidebar;
    private JPanel leftSidebarWrapper;
    private RightSidebar rightSidebar;
    private JPanel rightSidebarWrapper;

    /**
     * Creates a new instance of SubterfugeEditor
     */
    public SubterfugeEditor() {
        frame = new JFrame(NAME);
        frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.LINE_AXIS));
        frame.setMinimumSize(new Dimension(250, 250));
        windowListener = new SubterfugeWindowListener();
        frame.addComponentListener(windowListener);
        frame.addWindowStateListener(windowListener);

        menuBar = new JMenuBar();

        fileMenu = new JMenu("File");
        fileNewItem = new JMenuItem("New");
        fileNewItem.addActionListener(fileMenuListener);
        fileOpenItem = new JMenuItem("Open");
        fileOpenItem.addActionListener(fileMenuListener);
        fileSaveItem = new JMenuItem("Save");
        fileSaveItem.addActionListener(fileMenuListener);
        fileSaveItem.setEnabled(false);
        fileSaveAsItem = new JMenuItem("Save As");
        fileSaveAsItem.setEnabled(false);
        fileSaveAsItem.addActionListener(fileMenuListener);

        fileMenu.add(fileNewItem);
        fileMenu.add(fileOpenItem);
        fileMenu.add(fileSaveItem);
        fileMenu.add(fileSaveAsItem);
        fileMenu.setEnabled(false);

        resourceMenu = new JMenu("Resource");
        loadResourceItem = new JMenuItem("Load");
        loadResourceItem.addActionListener(resMenuListener);
        resourceMenu.add(loadResourceItem);
        resourceMenu.addSeparator();
        tileMenu = new JMenu("Tile");
        editTileItem = new JMenuItem("Edit");
        tileMenu.add(editTileItem);
        tileMenu.setEnabled(false);
        resourceMenu.add(tileMenu);

        levelMenu = new JMenu("Level");
        levelExpandItem = new JMenuItem("Expand");
        levelExpandItem.addActionListener(levelMenuListener);
        levelCropItem = new JMenuItem("Crop");
        levelCropItem.addActionListener(levelMenuListener);
        levelMenu.add(levelExpandItem);
        levelMenu.add(levelCropItem);
        levelMenu.setEnabled(false);

        menuBar.add(fileMenu);
        menuBar.add(resourceMenu);
        menuBar.add(levelMenu);

        frame.setJMenuBar(menuBar);

        viewer = new ViewerWindow(this);
        viewerWrapper = new JPanel(new BorderLayout());
        Border raisedBevel = BorderFactory.createRaisedBevelBorder();
        Border loweredBevel = BorderFactory.createLoweredBevelBorder();
        Border border = BorderFactory.createCompoundBorder(raisedBevel,
                loweredBevel);
        viewerWrapper.setBorder(border);
        viewerWrapper.add(viewer, BorderLayout.CENTER);

        leftSidebar = new LeftSidebar();
        leftSidebarWrapper = new JPanel(new BorderLayout());
        leftSidebarWrapper.setBorder(border);
        leftSidebarWrapper.add(leftSidebar, BorderLayout.CENTER);

        rightSidebar = new RightSidebar();
        rightSidebar.setBorder(BorderFactory.createEtchedBorder());
        rightSidebarWrapper = new JPanel();
        rightSidebarWrapper.setLayout(new BoxLayout(rightSidebarWrapper, BoxLayout.PAGE_AXIS));
        rightSidebarWrapper.setBorder(border);
        rightSidebarWrapper.add(rightSidebar);

        frame.add(leftSidebarWrapper);
        frame.add(viewerWrapper);
        frame.add(rightSidebarWrapper);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    /**
     * Initiates the SubterfugeEditor. Called at the start of the run() method.
     */
    private void init() {
        // Display the GUI
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        setSizes();
        frame.revalidate();
    }

    /**
     * Runs the SubterfugeEditor object.
     */
    @Override
    public void run() {

        // Initialize the editor
        init();

        // Say that we are running
        boolean running = true;

        while (running) {
            long lastTime = System.nanoTime();
            double nsPerTick = 1E9 / 60.0;

            long lastTimer = System.currentTimeMillis();
            double delta = 0.0;

            // Primary game loop
            while (running) {
                long now = System.nanoTime();
                delta += (now - lastTime) / nsPerTick;
                lastTime = now;

                while (delta >= 1) {
                    tick();
                    render();
                    delta -= 1;
                }

                if (System.currentTimeMillis() - lastTimer >= 1E3) {
                    lastTimer += 1000;
                }

                // Saves CPU cycles by sleeping
                now = System.nanoTime();
                delta += (now - lastTime) / nsPerTick;
                lastTime = now;
                if (delta < 0.95) {
                    try {
                        Thread.yield();
                        Thread.sleep(Math.round(Math
                                .floor(1E3 / 60 * (1 - delta) * 0.98)));
                    }
                    catch (InterruptedException e) {
                        logger.log(e);
                    }
                }
            }
        }

    }

    /**
     * Renders the various dynamic components of the editor.
     */
    private void render() {
        viewer.render();
    }

    /**
     * Manually sets the sizes of each component based on the current window
     * size.
     */
    private void setSizes() {
        int width = frame.getWidth() - frame.getInsets().left - frame.getInsets().right;
        int height = frame.getHeight() - frame.getInsets().top - frame.getInsets().bottom;

        int sidebarWidth = (int) (width * 0.15);
        int viewerWrapperWidth = width - sidebarWidth * 2;

        viewerWrapper.setPreferredSize(new Dimension(viewerWrapperWidth, height));
        leftSidebarWrapper.setPreferredSize(new Dimension(sidebarWidth, height));
        rightSidebarWrapper.setPreferredSize(new Dimension(sidebarWidth, height));
    }

    /**
     * Ticks and increments the state of the components involved in the editor.
     */
    @Override
    public void tick() {
        if (animateTiles) {
            Tile.tickTiles();
        }
    }

    // Need to change to account for loading new resources while the current ones are active
    /**
     * Opens a Dialog for the user to select a new set of resources to load and
     * then loads those resources into the static Resources class for access by
     * the rest of the program.
     */
    private void loadNewResources() {

        // Intifiy the resource file that the user wants to load
        JFileChooser chooser = new JFileChooser("Select a Resource INI File");
        chooser.setFileFilter(new IniFilter());
        chooser.setDialogTitle("Select a Resource INI File");
        String path = Paths.get("").toFile().getAbsolutePath();
        chooser.setCurrentDirectory(new File(path));
        chooser.showDialog(frame, "Load");

        File newRes = chooser.getSelectedFile();

        // If no file was selected, exit
        if (newRes == null) {
            return;
        }

        // Load the data to the Resources class
        ResourceInputFile resFile = new ResourceInputFile(newRes);
        resFile.readData();

        // Load the Tiles into the editor
        loadTiles();

        // Set the menus that were previously inactive active
        fileMenu.setEnabled(true);

        // Disable this menu option, as loading new Resources without restarting is not yet supported
        loadResourceItem.setEnabled(false);
    }

    /**
     * Loads the current newTiles stored in the Resources class into the editor
     * for
     * use by the user.
     */
    private void loadTiles() {
        for (String tileName : Resources.getTileNames()) {
            tiles.add(Resources.getTile(tileName));
        }
        Collections.sort(tiles);

        rightSidebar.loadTiles(tiles);

//        tileMenu.setEnabled(true);
    }

    /**
     * Creates a new, blank level and loads it into the editor.
     */
    private void createNewLevel() {
        Tile[][] newTiles = new Tile[64][64];
        for (int i = 0; i < 64; i++) {
            for (int j = 0; j < 64; j++) {
                newTiles[i][j] = Resources.getTile("VOID");
            }
        }
        Entity[][] entities = new Entity[64][64];
        LevelMap map = new LevelMap(newTiles, null);
        EntityMap entMap = new EntityMap(entities);
        level = new Level("NEW_LEVEL", map, entMap, 16, null);

        loadLevel(level);

        fileSaveItem.setEnabled(false);
    }

    /**
     * Loads the provided level into the editor
     *
     * @param level the level to be loaded into the editor
     */
    public void loadLevel(Level level) {
        this.level = level;
        viewer.loadLevel(level);
        levelMenu.setEnabled(true);
        fileSaveItem.setEnabled(true);
        fileSaveAsItem.setEnabled(true);
        leftSidebar.loadLevel(level);
    }

    /**
     * Accesses the currently selected tile by the user.
     *
     * @return Returns a Tile object of the currently selected tile by the user
     *         from the tile selection side bar.
     */
    public Tile currentTile() {
        return rightSidebar.currentTile();
    }

    /**
     * Saves the resource configuration file, updating for any changes that have occurred.
     */
    public void saveResourceConfig() {
        Resources.saveConfig();
    }

    /**
     * Creates a new instance of the SubterfugeEditor class and begins running
     * it.
     *
     * @param args the command line arguments.
     */
    public static void main(String args[]) {
        new Thread(new SubterfugeEditor()).start();
    }

    /**
     * A FileFilter for INI File selection dialogs
     */
    public static class IniFilter extends FileFilter {

        /**
         * Empty default constructor
         */
        public IniFilter() {

        }

        /**
         * Tests a provided file for acceptance by the filter. Directories and
         * files ending in .ini are accepted, anything else is not.
         *
         * @param file the file to be checked for acceptance
         *
         * @return Returns true if the file should be accepted, false if it
         *         should not.
         */
        @Override
        public boolean accept(File file) {
            if (file.isDirectory()) {
                return true;
            }
            String path = file.getAbsolutePath();
            int pointIndex = path.lastIndexOf('.');
            String ext = path.substring(pointIndex + 1);
            if (ext == null) {
                return false;
            }
            return ext.equalsIgnoreCase("INI");
        }

        /**
         * Provides the description of the filter for the file chooser.
         *
         * @return Returns the description of the filter: "Input files (.ini)"
         */
        @Override
        public String getDescription() {
            return "Input Files (.ini)";
        }

    }

    /**
     * A listener that reacts to window events for the editor.
     */
    private class SubterfugeWindowListener implements ComponentListener, WindowStateListener {

        @Override
        public void componentResized(ComponentEvent e) {
            setSizes();
        }

        @Override
        public void componentMoved(ComponentEvent e) {

        }

        @Override
        public void componentShown(ComponentEvent e) {
            setSizes();
        }

        @Override
        public void componentHidden(ComponentEvent e) {
            setSizes();
        }

        @Override
        public void windowStateChanged(WindowEvent e) {
            setSizes();
        }

    }

    /**
     * A listener that reacts to input from the File menu.
     */
    private class FileMenuListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source == fileNewItem) {
                createNewLevel();
            }
            else if (source == fileOpenItem) {
                Collection<String> namesCol = Resources.getLevelNames();
                String[] names = new String[namesCol.size()];
                int index = 0;
                for (String name : namesCol) {
                    names[index] = name;
                    index++;
                }
                String name = (String) JOptionPane.showInputDialog(frame, "Select a Level to Load", "Load Level", JOptionPane.PLAIN_MESSAGE, null, names, names[0]);
                Level level = Resources.getLevel(name);
                if (level != null) {
                    loadLevel(level);
                }
            }
            else if (source == fileSaveItem) {
                level.save();
                saveResourceConfig();
            }
            else if (source == fileSaveAsItem) {
                // Get the String of the path to where the file is to be saved
                JFileChooser chooser = new JFileChooser();
                chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                chooser.setDialogTitle("Select a Save Location");
                String path = Paths.get("").toFile().getAbsolutePath();
                chooser.setCurrentDirectory(new File(path));
                chooser.showDialog(frame, "Save");

                File saveDir = chooser.getSelectedFile();

                level.setFilePath(saveDir.getAbsolutePath() + "\\level.cfg");
                level.save(saveDir.getAbsolutePath());

                Resources.addLevel(level.getName(), level);

                saveResourceConfig();

                fileSaveItem.setEnabled(true);
            }
        }

    }

    /**
     * A listener that reacts to input form the Resource menu.
     */
    private class ResourceMenuListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source == loadResourceItem) {
                loadNewResources();
            }
        }

    }

    /**
     * A listener that reacts to input from the Level Menu
     */
    private class LevelMenuListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();

            if (source == levelExpandItem) {
                ExpandLevelDialog dialog = new ExpandLevelDialog(frame, level);
                dialog.setLocationRelativeTo(viewer);
                dialog.setVisible(true);

                LevelResize resize = dialog.getResize();
                if (resize != null) {
                    level.applyResize(resize);
                }
            }
            else if (source == levelCropItem) {
                CropLevelDialog dialog = new CropLevelDialog(frame, level);
                dialog.setLocationRelativeTo(viewer);
                dialog.setVisible(true);

                LevelResize resize = dialog.getResize();
                if (resize != null) {
                    level.applyResize(resize);
                }
            }
        }

    }

}
